import * as THREE from "three";

// 引入轨道控制器控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// 引入动画库
import gsap from "gsap";
// 引入数据图形用户界面库
import * as dat from "dat.gui";
// 引入rgbeLoad
// DataTextureLoader
// 用于加载二进制文件格式的(rgbe, hdr, ...)的抽象类。 内部使用FileLoader来加载文件， 和创建一个新的 DataTexture.
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"


console.log(THREE);

//three.js 基本内容

// 1.创建场景  场景能够让你在什么地方、摆放什么东西来交给three.js来渲染，这是你放置物体、灯光和摄像机的地方。
const scene = new THREE.Scene();

// 2.创建相机 

//  透视相机（PerspectiveCamera）这一摄像机使用perspective projection（透视投影）来进行投影。
// 这一投影模式被用来模拟人眼所看到的景象，它是3D场景的渲染中使用得最普遍的投影模式。
// PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )
// fov — 摄像机视锥体垂直视野角度   // aspect — 摄像机视锥体长宽比  // near — 摄像机视锥体近端面  // far — 摄像机视锥体远端面
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

//2.1设置相机的位置 position表示对象局部位置的Vector3。默认值为(0, 0, 0)。

// Vector3( x : Float, y : Float, z : Float )   该类表示的是一个三维向量（3D vector）。'
//  一个三维向量表示的是一个有顺序的、三个为一组的数字组合（标记为x、y和z）， 可被用来表示很多事物:
//  一个位于三维空间中的点。
// 一个在三维空间中的方向与长度的定义。在three.js中，长度总是从(0, 0, 0)到(x, y, z)的 Euclidean distance（欧几里德距离，即直线距离）， 
// 方向也是从(0, 0, 0)到(x, y, z)的方向。
// .set ( x : Float, y : Float, z : Float ) : this
// 设置该向量的x、y 和 z 分量。


camera.position.set(0, 0, 10);
scene.add(camera); //添加相机到场景中

// 3.创建物体 其他种类的几何体  
// 初始化球体与材质
const geometry = new THREE.SphereGeometry(2, 32, 16);

const standardMaterial = new THREE.MeshStandardMaterial();
const mesh = new THREE.Mesh(geometry, standardMaterial);
// // 设置球体投射阴影
// .castShadow : Boolean
// 对象是否被渲染到阴影贴图中。默认值为false。
mesh.castShadow = true;
scene.add(mesh);

// 创建平面接收阴影
const planeGeometry = new THREE.PlaneGeometry(50,50);
const mesh1 = new THREE.Mesh(planeGeometry, standardMaterial);
mesh1.position.set(0,-2,0);
mesh1.rotation.x = - Math.PI / 2;
// 设置平面接收投射阴影
// .receiveShadow : Boolean
// 材质是否接收阴影。默认值为false。
mesh1.receiveShadow = true;
scene.add(mesh1);


// 添加灯光
// 添加环境光
// AmbientLight 环境光会均匀的照亮场景中的所有物体。环境光不能用来投射阴影，因为它没有方向。
// AmbientLight( color : Color, intensity : Float )
// color -（可选）一个表示颜色的 Color 的实例、字符串或数字，默认为一个白色（0xffffff）的 Color 对象。
// intensity -（可选）光照的强度。默认值为 1。
const light = new THREE.AmbientLight(0xffffff, 0.5); 

// 添加聚光灯
// 聚光灯（SpotLight）
// 光线从一个点沿一个方向射出，随着光线照射的变远，光线圆锥体的尺寸也逐渐增大。
// SpotLight( color : Color, intensity : Float, distance : Float, angle : Radians, penumbra : Float, decay : Float )
// color -（可选）一个表示颜色的 Color 的实例、字符串或数字，默认为一个白色（0xffffff）的 Color 对象。
// intensity -（可选）光照强度。默认值为 1。
// distance - 光源照射的最大距离。默认值为 0（无限远）。
// angle - 光线照射范围的角度。默认值为 Math.PI/3。
// penumbra - 聚光锥的半影衰减百分比。默认值为 0。
// decay - 沿着光照距离的衰减量。默认值为 2。
const spotLight = new THREE.SpotLight(0xffffff, 2);
// // 设置平行光光源位置
spotLight.position.set(10, 10, 10);
// 设置光源投射阴影
// .castShadow : Boolean
// 此属性设置为 true 灯光将投射阴影。注意：这样做的代价比较高，需要通过调整让阴影看起来正确。 
// 查看 DirectionalLightShadow 了解详细信息。 默认值为 false。
spotLight.castShadow = true;

// 设置投射阴影贴图的模糊度
// .radius : Float
// 将此值设置为大于1的值将模糊阴影的边缘。
// 较高的值会在阴影中产生不必要的条带效果 - 更大的mapSize将允许在这些效果变得可见之前使用更高的值。
spotLight.shadow.radius = 40
// 设置投射阴影贴图的分辨率
// .mapSize : Vector2
// 一个Vector2定义阴影贴图的宽度和高度。
// 较高的值会以计算时间为代价提供更好的阴影质量。值必须是2的幂，直到给定设备的WebGLRenderer.capabilities.maxTextureSize， 
// 虽然宽度和高度不必相同（例如，（512,1024）有效）。 默认值为（512,512）。
spotLight.shadow.mapSize.set(512*6,512*6);
// .target : Object3D
// 灯光从它的位置（position）指向目标位置。默认的目标位置为(0, 0, 0)。
// 注意：对于目标的位置，如果要改为除默认值之外的其他位置，该位置必须被添加到场景（scene）中去。
spotLight.target = mesh;
// .decay : Float
// 光线随着距离增加变暗的衰减量。默认值为 2。
// 在物理正确渲染的上下文中，不应更改默认值。
spotLight.decay = 0;
// .angle : Float
// 光线照射范围的角度，用弧度表示。不应超过 Math.PI/2。默认值为 Math.PI/3
spotLight.angle = Math.PI / 6;
// .distance : Float  有效距离
// 当值为零时，光线将根据平方反比定律衰减到无限远。 
// 当值不为零时，光线会先按照平方反比定律衰减，直到距离截止点附近，然后线性衰减到 0。
spotLight.distance = 0;
// .penumbra : Float
// 该属性设置照明区域在边缘附近的平滑衰减速度，取值范围在 0 到 1 之间。默认值为 0.0。
spotLight.penumbra = 0;

scene.add(light);
scene.add(spotLight);

const gui = new dat.GUI();


gui.add(mesh.position, "x").min(-5).max(5).step(0.1);
gui.add(spotLight, "decay").min(0).max(5).step(0.01);
gui
  .add(spotLight, "angle")
  .min(0)
  .max(Math.PI / 2)
  .step(0.01);
gui.add(spotLight, "distance").min(0).max(20).step(0.01);
gui.add(spotLight, "penumbra").min(0).max(1).step(0.01);
// gui.add(directionalLight.shadow.camera,"near").min(0).max(20).step(0.1).onChange(()=>{
//     // 在大多数属性发生改变之后，你将需要调用.updateProjectionMatrix来使得这些改变生效。
//     directionalLight.shadow.camera.updateProjectionMatrix();
// });;

// 4.渲染
// 初始化渲染器
// WebGLRenderer WebGL Render 用WebGL渲染出你精心制作的场景。
const renderer = new THREE.WebGLRenderer();
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// .domElement : DOMElement   一个canvas，渲染器在其上绘制输出。
// 渲染器的构造函数会自动创建(如果没有传入canvas参数);你需要做的仅仅是像下面这样将它加页面里去:
document.body.appendChild(renderer.domElement); //将绘制canvas添加到页面里
// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true;
// renderer.physicallyCorrectLights = true;  新版本不用设置 物理正确光源 decay也会生效。


//创建轨道控制器
// OrbitControls( object : Camera, domElement : HTMLDOMElement )
// object: （必须）将要被控制的相机。该相机不允许是其他任何对象的子级，除非该对象是场景自身。
// domElement: 用于事件监听的HTML元素。
const controls = new OrbitControls(camera, renderer.domElement);
// 将其设置为true以启用阻尼（惯性），这将给控制器带来重量感。默认值为false。
// 请注意，如果该值被启用，你将必须在你的动画循环里调用.update()。
controls.enableDamping = true;

// 创建坐标轴辅助器
// AxesHelper  用于简单模拟3个坐标轴的对象.
// 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
// AxesHelper( size : Number )  size -- (可选的) 表示代表轴的线段长度. 默认为 1.
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);


function render() {
    controls.update();
    renderer.render(scene, camera);
    // requestAnimationFrame 是一个用于优化浏览器动画效果的 API。它可以让浏览器在下一次重绘前执行指定的回调函数，
    // 从而可以更加流畅地执行动画效果，避免了使用 setTimeout 或 setInterval 可能引起的性能问题。
    requestAnimationFrame(render);
}

render()

// 窗口变化时，更新渲染画面
window.addEventListener("resize", () => {

    // 更新摄像机视锥体长宽比
    camera.aspect = window.innerWidth / window.innerHeight;
    // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用
    camera.updateProjectionMatrix();

    // 更新渲染器
    renderer.setSize(window.innerWidth, window.innerHeight);
    // 设置渲染器像素比 通常用于避免HiDPI设备上绘图模糊
    renderer.setPixelRatio(window.devicePixelRatio);

})


